<template>
  <component :is="wrapperComponent" :offset-top="offsetTop" :offset-bottom="offsetBottom" @on-change="handleAffixStateChange">
    <div :class="`${prefix}-wrapper`" :style="wrapperStyle">
      <div :class="`${prefix}`">
        <div :class="`${prefix}-ink`">
          <span v-show="showInk" :class="`${prefix}-ink-ball`" :style="{top: `${inkTop}px`}"></span>
        </div>
        <slot></slot>
      </div>
    </div>
  </component>
</template>
<script>
import { scrollTop, findComponentsDownward, sharpMatcherRegx } from '../../utils/assist';
import { on, off } from '../../utils/dom';
export default {
  name: 'Anchor',
  provide() {
    return {
      anchorCom: this
    };
  },
  props: {
    affix: {
      type: Boolean,
      default: true
    },
    offsetTop: {
      type: Number,
      default: 0
    },
    offsetBottom: Number,
    bounds: {
      type: Number,
      default: 5
    },
    //        container: [String, HTMLElement],  // HTMLElement 在 SSR 下不支持
    container: null,
    showInk: {
      type: Boolean,
      default: false
    },
    scrollOffset: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      prefix: 'ivu-anchor',
      isAffixed: false, // current affixed state
      inkTop: 0,
      animating: false, // if is scrolling now
      currentLink: '', // current show link =>  #href -> currentLink = #href
      currentId: '', // current show title id =>  #href -> currentId = href
      scrollContainer: null,
      scrollElement: null,
      titlesOffsetArr: [],
      wrapperTop: 0,
      upperFirstTitle: true
    };
  },
  mounted() {
    this.init();
  },
  beforeDestroy() {
    this.removeListener();
  },
  methods: {
    handleAffixStateChange(state) {
      this.isAffixed = this.affix && state;
    },
    handleScroll(e) {
      this.upperFirstTitle = e.target.scrollTop < this.titlesOffsetArr[0].offset;
      if (this.animating) return;
      this.updateTitleOffset();
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop || e.target.scrollTop;
      this.getCurrentScrollAtTitleId(scrollTop);
    },
    handleHashChange() {
      const url = window.location.href;
      const sharpLinkMatch = sharpMatcherRegx.exec(url);
      if (!sharpLinkMatch) return;
      this.currentLink = sharpLinkMatch[0];
      this.currentId = sharpLinkMatch[1];
    },
    handleScrollTo() {
      const anchor = document.getElementById(this.currentId);
      const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`);
      let offset = this.scrollOffset;
      if (currentLinkElementA) {
        offset = parseFloat(currentLinkElementA.getAttribute('data-scroll-offset'));
      }

      if (!anchor) return;
      const offsetTop = anchor.offsetTop - this.wrapperTop - offset;
      this.animating = true;
      scrollTop(this.scrollContainer, this.scrollElement.scrollTop, offsetTop, 600, () => {
        this.animating = false;
      });
      this.handleSetInkTop();
    },
    handleSetInkTop() {
      const currentLinkElementA = document.querySelector(`a[data-href="${this.currentLink}"]`);
      if (!currentLinkElementA) return;
      const elementATop = currentLinkElementA.offsetTop;
      const top = elementATop < 0 ? this.offsetTop : elementATop;
      this.inkTop = top;
    },
    updateTitleOffset() {
      const links = findComponentsDownward(this, 'AnchorLink').map(link => {
        return link.href;
      });
      const idArr = links.map(link => {
        return link.split('#')[1];
      });
      let offsetArr = [];
      idArr.forEach(id => {
        const titleEle = document.getElementById(id);
        if (titleEle) {
          offsetArr.push({
            link: `#${id}`,
            offset: titleEle.offsetTop - this.scrollElement.offsetTop
          }); 
        }
      });
      this.titlesOffsetArr = offsetArr;
    },
    getCurrentScrollAtTitleId(scrollTop) {
      let i = -1;
      let len = this.titlesOffsetArr.length;
      let titleItem = {
        link: '#',
        offset: 0
      };
      scrollTop += this.bounds;
      while (++i < len) {
        let currentEle = this.titlesOffsetArr[i];
        let nextEle = this.titlesOffsetArr[i + 1];
        if (scrollTop >= currentEle.offset && scrollTop < ((nextEle && nextEle.offset) || Infinity)) {
          titleItem = this.titlesOffsetArr[i];
          break;
        }
      }
      this.currentLink = titleItem.link;
      this.handleSetInkTop();
    },
    getContainer() {
      this.scrollContainer = this.container ? (typeof this.container === 'string' ? document.querySelector(this.container) : this.container) : window;
      this.scrollElement = this.container ? this.scrollContainer : document.documentElement || document.body;
    },
    removeListener() {
      off(this.scrollContainer, 'scroll', this.handleScroll);
      off(window, 'hashchange', this.handleHashChange);
    },
    init() {
      // const anchorLink = findComponentDownward(this, 'AnchorLink');
      this.handleHashChange();
      this.$nextTick(() => {
        this.removeListener();
        this.getContainer();
        this.wrapperTop = this.containerIsWindow ? 0 : this.scrollElement.offsetTop;
        this.handleScrollTo();
        this.handleSetInkTop();
        this.updateTitleOffset();
        if (this.titlesOffsetArr[0]) {
          this.upperFirstTitle = this.scrollElement.scrollTop < this.titlesOffsetArr[0].offset;
        }
        on(this.scrollContainer, 'scroll', this.handleScroll);
        on(window, 'hashchange', this.handleHashChange);
      });
    }
  },
  computed: {
    wrapperComponent() {
      return this.affix ? 'Affix' : 'div';
    },
    wrapperStyle() {
      return {
        maxHeight: this.offsetTop ? `calc(100vh - ${this.offsetTop}px)` : '100vh'
      };
    },
    containerIsWindow() {
      return this.scrollContainer === window;
    }
  },
  watch: {
    $route() {
      this.handleHashChange();
      this.$nextTick(() => {
        this.handleScrollTo();
      });
    },
    container() {
      this.init();
    },
    currentLink(newHref, oldHref) {
      this.$emit('on-change', newHref, oldHref);
    }
  }
};
</script>
